import datetime
import os
import random

import jwt
import ujson
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfo
from alipay.aop.api.domain.SettleInfo import SettleInfo
from alipay.aop.api.request import AlipayTradeQueryRequest
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from django.core.cache import caches
from django.db.models import Prefetch, Q
from django.db.transaction import atomic
from django.http import HttpResponse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django_filters.rest_framework import DjangoFilterBackend
from django_redis import get_redis_connection
from rest_framework.decorators import api_view, action, authentication_classes
from rest_framework.filters import OrderingFilter
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from api.consts import MAX_PHOTO_SIZE, FILE_UPLOAD_SUCCESS, FILE_SIZE_EXCEEDED, \
    CODE_TOO_FREQUENCY, MOBILE_CODE_SUCCESS, INVALID_TEL_NUM, USER_LOGIN_SUCCESS, \
    USER_LOGIN_FAILED, INVALID_LOGIN_INFO
from api.helpers import EstateFilterSet, HouseInfoFilterSet, DefaultResponse, \
    LoginRequiredAuthentication, RbacPermission
from api.serializers import DistrictSimpleSerializer, DistrictDetailSerializer, \
    AgentCreateSerializer, AgentDetailSerializer, AgentSimpleSerializer, \
    HouseTypeSerializer, TagSerializer, EstateCreateSerializer, \
    EstateDetailSerializer, EstateSimpleSerializer, HouseInfoDetailSerializer, \
    HousePhotoSerializer, HouseInfoCreateSerializer, HouseInfoSimpleSerializer, \
    UserCreateSerializer, UserUpdateSerializer, UserSimpleSerializer
from common.consts import alipay_client
from common.models import District, Agent, HouseType, Tag, User, LoginLog, \
    HousePhoto, Estate, HouseInfo
from common.utils import gen_mobile_code, send_sms_by_luosimao, to_md5_hex, \
    get_ip_address, upload_stream_to_qiniu, gen_qrcode
from common.validators import check_tel, check_username, check_email
from zufang.settings import SECRET_KEY


def get_qrcode(request):
    data = gen_qrcode('https://www.zhihu.com/people/jackfrued')
    return HttpResponse(data, content_type='image/png')


@api_view(('GET', ))
# @authentication_classes((LoginRequiredAuthentication, ))
def get_payment_page(request, houseid):
    # https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay
    model = AlipayTradePagePayModel()
    # 产品订单号
    model.out_trade_no = '202003051646520001'
    # 订单总金额
    model.total_amount = 200
    # 订单主题
    model.subject = '租房订金'
    # model.body = '支付宝测试'
    # 销售产品码，与支付宝签约的产品码名称。 注：目前仅支持FAST_INSTANT_TRADE_PAY
    model.product_code = 'FAST_INSTANT_TRADE_PAY'
    # 结算详细信息
    settle_detail_info = SettleDetailInfo()
    settle_detail_info.amount = 200
    # 结算收款方的账户类型
    settle_detail_info.trans_in_type = 'cardAliasNo'
    # 结算收款方
    settle_detail_info.trans_in = '6216603100004601162'
    settle_detail_infos = list()
    settle_detail_infos.append(settle_detail_info)
    # 结算信息
    settle_info = SettleInfo()
    settle_info.settle_detail_infos = settle_detail_infos
    model.settle_info = settle_info
    # sub_merchant = SubMerchant()
    # sub_merchant.merchant_id = '2088102180149774'
    # model.sub_merchant = sub_merchant
    request = AlipayTradePagePayRequest(biz_model=model)
    url = alipay_client.page_execute(request, http_method='GET')
    # 此处应该补充生成交易流水的代码 ---> Model
    # 将用户信息、房源信息、交易编号、交易金额、交易时间、交易状态、……写入数据库

    # 还需要一个设计一个接口作为接收通知的接口其中包括对订单交易结果的查询
    # 最后还要更新之前的交易流水信息
    # request = AlipayTradeQueryRequest(biz_model=model)
    # content = alipay_client.page_execute(request)
    return DefaultResponse(data={'url': url})


@api_view(('POST', ))
def upload_house_photo(request):
    file_obj = request.FILES.get('mainphoto')
    if file_obj and len(file_obj) < MAX_PHOTO_SIZE:
        prefix = to_md5_hex(file_obj.file)
        filename = f'{prefix}{os.path.splitext(file_obj.name)[1]}'
        upload_stream_to_qiniu.delay(file_obj, filename, len(file_obj))
        photo = HousePhoto()
        photo.path = f'http://q69nr46pe.bkt.clouddn.com/{filename}'
        photo.ismain = True
        photo.save()
        resp = DefaultResponse(*FILE_UPLOAD_SUCCESS, data={
            'photoid': photo.photoid,
            'url': photo.path
        })
    else:
        resp = DefaultResponse(*FILE_SIZE_EXCEEDED)
    return resp


@api_view(('GET', ))
def get_code_by_sms(request, tel):
    """获取短信验证码"""
    if check_tel(tel):
        if caches['default'].get(f'{tel}:block'):
            resp = DefaultResponse(*CODE_TOO_FREQUENCY)
        else:
            code = gen_mobile_code()
            message = f'您的短信验证码是{code}，打死也不能告诉别人哟！【Python小课】'
            send_sms_by_luosimao.apply_async((tel, message),
                                             countdown=random.random() * 5)
            caches['default'].set(f'{tel}:block', code, timeout=120)
            caches['default'].set(f'{tel}:valid', code, timeout=1800)
            resp = DefaultResponse(*MOBILE_CODE_SUCCESS)
    else:
        resp = DefaultResponse(*INVALID_TEL_NUM)
    return resp


@api_view(('POST', ))
def login(request):
    """登录（获取用户身份令牌）"""
    username = request.data.get('username')
    password = request.data.get('password')
    if (check_username(username) or check_tel(username) or
            check_email(username)) and len(password) >= 6:
        password = to_md5_hex(password)
        q = Q(username=username, password=password) | \
            Q(tel=username, password=password) | \
            Q(email=username, password=password)
        user = User.objects.filter(q)\
            .only('username', 'realname').first()
        if user:
            # 用户登录成功通过JWT生成用户身份令牌
            payload = {
                'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1),
                'data': {
                    'userid': user.userid,
                }
            }
            token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode()
            with atomic():
                current_time = timezone.now()
                if not user.lastvisit or \
                        (current_time - user.lastvisit).days >= 1:
                    user.point += 2
                    user.lastvisit = current_time
                    user.save()
                loginlog = LoginLog()
                loginlog.user = user
                loginlog.logdate = current_time
                loginlog.ipaddr = get_ip_address(request)
                loginlog.save()
            resp = DefaultResponse(*USER_LOGIN_SUCCESS, data={
                # 请求令牌（有效期比较短，通常是1-2个小时左右；刷新令牌的有效期通常都比较长）
                'token': token,
                # 刷新令牌 - 如果用户的请求令牌过期，服务器会返回401，浏览器收到401后将刷新令牌发给服务器，
                # 服务器如果判定刷新令牌没有过期就会重新给用户签发请求令牌，并保存到浏览器本地存储中，
                # 如果服务器发现刷新令牌已经过期，就会返回403，这时候浏览器会提示用户登录信息过期需要重新登录
                # 'refresh_token': refresh_token,
                'username': user.username,
                'realname': user.realname
            })
        else:
            resp = DefaultResponse(*USER_LOGIN_FAILED)
    else:
        resp = DefaultResponse(*INVALID_LOGIN_INFO)
    return resp


@api_view(('DELETE', ))
def logout(request):
    """注销（销毁用户身份令牌）"""
    # 如果使用了JWT这种方式通过令牌进行用户身份认证
    # 如何彻底让令牌失效??? ---> Redis用集合类型做一个失效令牌清单
    # 定时任务从失效令牌清单中清理过期令牌避免集合元素过多
    pass


@cache_page(timeout=365 * 86400)
@api_view(('GET', ))
def get_provinces(request):
    """获取省级行政单位"""
    queryset = District.objects.filter(parent__isnull=True)\
        .only('name')
    serializer = DistrictSimpleSerializer(queryset, many=True)
    return Response({
        'code': 10000,
        'message': '获取省级行政区域成功',
        'results': serializer.data
    })


@api_view(('GET', ))
def get_district(request, distid):
    """获取地区详情"""
    redis_cli = get_redis_connection()
    data = redis_cli.get(f'zufang:district:{distid}')
    if data:
        data = ujson.loads(data)
    else:
        district = District.objects.filter(distid=distid)\
            .defer('parent').first()
        data = DistrictDetailSerializer(district).data
        redis_cli.set(f'zufang:district:{distid}', ujson.dumps(data), ex=900)
    return Response(data)


@method_decorator(decorator=cache_page(timeout=86400), name='get')
class HotCityView(ListAPIView):
    """热门城市视图
    get:
        获取热门城市
    """
    queryset = District.objects.filter(ishot=True).only('name')
    serializer_class = DistrictSimpleSerializer
    pagination_class = None


@method_decorator(decorator=cache_page(timeout=120), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class AgentViewSet(ModelViewSet):
    """经理人视图
    list:
        获取经理人列表
    retrieve:
        获取经理人详情
    create:
        创建经理人
    update:
        更新经理人信息
    partial_update:
        更新经理人信息
    delete:
        删除经理人
    """
    queryset = Agent.objects.all()
    # 认证类（接口认证）
    authentication_classes = (LoginRequiredAuthentication, )
    # 授权类（接口授权）
    permission_classes = (RbacPermission, )

    # 因为需要根据查询参数（URL中问号后面的参数）做数据筛选，所以重新了get_queryset方法定制queryset
    def get_queryset(self):
        name = self.request.GET.get('name')
        if name:
            self.queryset = self.queryset.filter(name__startswith=name)
        servstar = self.request.GET.get('servstar')
        if servstar:
            self.queryset = self.queryset.filter(servstar__gte=servstar)
        if self.action == 'list':
            self.queryset = self.queryset.only('name', 'tel', 'servstar')
        else:
            self.queryset = self.queryset.prefetch_related(
                Prefetch('estates',
                         queryset=Estate.objects.all().only('name').order_by('-hot'))
            )
        return self.queryset.order_by('-servstar')

    def get_serializer_class(self):
        if self.action in ('create', 'update'):
            return AgentCreateSerializer
        return AgentDetailSerializer if self.action == 'retrieve' \
            else AgentSimpleSerializer


@method_decorator(decorator=cache_page(timeout=86400), name='list')
@method_decorator(decorator=cache_page(timeout=86400), name='retrieve')
class HouseTypeViewSet(ModelViewSet):
    """户型视图集"""
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer
    pagination_class = None


@method_decorator(decorator=cache_page(timeout=3600), name='list')
class TagViewSet(ModelViewSet):
    """房源标签视图集"""
    queryset = Tag.objects.all()
    serializer_class = TagSerializer


@method_decorator(decorator=cache_page(timeout=300), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class EstateViewSet(ModelViewSet):
    """楼盘视图集"""
    queryset = Estate.objects.all()
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filterset_class = EstateFilterSet
    ordering = '-hot'
    ordering_fields = ('district', 'hot', 'name')
    authentication_classes = (LoginRequiredAuthentication, )
    permission_classes = (RbacPermission, )

    def get_queryset(self):
        if self.action == 'list':
            queryset = self.queryset.only('name')
        else:
            queryset = self.queryset\
                .defer('district__parent', 'district__ishot', 'district__intro')\
                .select_related('district')
        return queryset

    def get_serializer_class(self):
        if self.action in ('create', 'update'):
            return EstateCreateSerializer
        return EstateDetailSerializer if self.action == 'retrieve' \
            else EstateSimpleSerializer


@method_decorator(decorator=cache_page(timeout=120), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class HouseInfoViewSet(ModelViewSet):
    """房源视图集"""
    queryset = HouseInfo.objects.all()
    serializer_class = HouseInfoDetailSerializer
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filterset_class = HouseInfoFilterSet
    ordering = ('-pubdate', )
    ordering_fields = ('pubdate', 'price')

    @action(methods=('GET', ), detail=True)
    def photos(self, request, pk):
        queryset = HousePhoto.objects.filter(house=self.get_object())
        return Response(HousePhotoSerializer(queryset, many=True).data)

    def get_queryset(self):
        if self.action == 'list':
            return self.queryset\
                .only('houseid', 'title', 'area', 'floor', 'totalfloor', 'price',
                      'mainphoto', 'priceunit', 'street', 'type',
                      'district_level3__distid', 'district_level3__name')\
                .select_related('district_level3', 'type')\
                .prefetch_related('tags')
        return self.queryset\
            .defer('user', 'district_level2',
                   'district_level3__parent', 'district_level3__ishot', 'district_level3__intro',
                   'estate__district', 'estate__hot', 'estate__intro',
                   'agent__realstar', 'agent__profstar', 'agent__certificated')\
            .select_related('district_level3', 'type', 'estate', 'agent')\
            .prefetch_related('tags')

    def get_serializer_class(self):
        if self.action in ('create', 'update'):
            return HouseInfoCreateSerializer
        return HouseInfoDetailSerializer if self.action == 'retrieve' \
            else HouseInfoSimpleSerializer


class UserViewSet(ModelViewSet):
    """用户模型视图集"""
    queryset = User.objects.all()

    def get_serializer_class(self):
        if self.action == 'create':
            return UserCreateSerializer
        elif self.action == 'update':
            return UserUpdateSerializer
        return UserSimpleSerializer
